import argon2 from 'argon2'
import crypto from 'crypto'
import iconvLite from 'iconv-lite'
import svgCaptcha from 'svg-captcha'
import { Injectable } from '@nestjs/common'
import { RedisService } from './redis.service'
import { ConfigService } from '@nestjs/config'
import { ConfigEnum, TimeUnit } from '@/common'

@Injectable()
export class SharedService {
  private _publicKey: string
  private _privateKey: string

  constructor(private readonly redisService: RedisService, private readonly configService: ConfigService) {
    this._publicKey = this.configService.get<string>(ConfigEnum.RSA_PUBLIC_KEY)!
    this._privateKey = this.configService.get<string>(ConfigEnum.RSA_PRIVATE_KEY)!
  }

  /** 生成图形验证码 */
  public async generateCaptcha() {
    const uuid = this.randomUUID().replaceAll('-', '').toLowerCase()
    const { data, text } = svgCaptcha.createMathExpr({ background: '#C0C8BE', noise: 4 })
    const captcha = `data:image/svg+xml;base64,${Buffer.from(data).toString('base64')}`
    await this.redisService.setEx(`captcha:${uuid}`, text, TimeUnit.MINUTE)
    return { uuid, captcha }
  }

  /** 验证图形验证码 */
  public async validateCaptcha(uuid: string, text: string) {
    const cacheKey = `captcha:${uuid}`
    const cacheText = await this.redisService.get(cacheKey) // 从 Redis 获取存储的验证码结果
    if (cacheText !== text) throw new Error('验证码错误，请刷新后重试') // 验证不通过则抛出错误
    await this.redisService.del(cacheKey) // 验证通过后删除验证码（防止重复使用）
    return true
  }

  /** 使用 Argon2 算法对用户密码进行加密 */
  public generateHashPwd(password?: string): Promise<string> {
    const config = this.configService.get<NestConfig.Server>(ConfigEnum.SERVER)
    const defaultPassword = config?.defaultPassword || '123456'
    return argon2.hash(password || defaultPassword) // 密码为空时使用默认密码
  }

  /** 使用 Argon2 算法验证密码与哈希值是否匹配 */
  public validateHashPwd(hashPassword: string, plainPassword: string): Promise<boolean> {
    return argon2.verify(hashPassword, plainPassword) // 验证明文密码与哈希值是否一致
  }

  /**
   * 使用公钥加密给定的数据
   * @param {string} data - 要加密的字符串数据。
   * @returns 加密后的数据，以 base64 编码的字符串形式返回。
   */
  public encryptWithPublicKey(data: string): string {
    const buffer = Buffer.from(data, 'utf-8')
    const encrypted = crypto.publicEncrypt(this._publicKey, buffer)
    return encrypted.toString('base64')
  }

  /**
   * 解密给定的加密数据
   * @param {string} encryptedData - 需要解密的 base64 编码的字符串
   * @returns 解密后的字符串，使用 UTF-8 编码
   */
  public decryptWithPrivateKey(encryptedData: string): string {
    const buffer = Buffer.from(encryptedData, 'base64')
    const decrypted = crypto.privateDecrypt(this._privateKey, buffer)
    return decrypted.toString('utf-8')
  }

  /**
   * 获取请求的 IP 地址
   * @param request - 包含请求信息的 Request 对象
   * @returns 请求的 IP 地址，如果未找到则返回空字符串
   */
  public getRequestIp(request: ExpressRequest): string {
    const xForwardedFor = request.headers['x-real-ip'] as string // 判断是否有反向代理 IP
    const socketAddress = request.socket.remoteAddress || '' // 判断后端的 socket 的 IP
    return (xForwardedFor || socketAddress).replace('::ffff:', '').replace('::1', '127.0.0.1') || ''
  }

  /**
   * 判断IP是不是内网
   * @param ip - 要判断的IP地址
   * @returns 如果是内网IP返回true，否则返回false
   */
  public isInternalIp(ip: string): boolean {
    const internalIpRegex = /^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.|127\.)/
    return internalIpRegex.test(ip) // 正则表达式匹配内网IP地址范围
  }

  /**
   * 通过 IP 地址查询地理位置信息
   * @param {string} ip - 需要查询的 IP 地址
   * @returns {Promise<string>} 地理位置信息，格式为"省份 城市"（如"北京市 北京市"）
   */
  public async getLocationByIP(ip: string) {
    try {
      if (this.isInternalIp(ip)) return '内网IP'
      const response = await fetch(`http://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`)
      const arrayBuffer = await response.arrayBuffer()
      const buffer = Buffer.from(arrayBuffer)
      const decodedData = iconvLite.decode(buffer, 'gbk')
      const data = JSON.parse(decodedData)
      // 拼接地址信息
      let address = ''
      if (data.pro) address += data.pro
      if (data.city) address += ` ${data.city}`
      return address || '未知'
    } catch (error) {
      return '未知'
    }
  }

  /**
   * 重命名对象的属性
   * @param target - 要修改的目标对象
   * @param oldKey - 要重命名的旧属性键
   * @param newKey - 新的属性键
   */
  public renameProperty(target: object, oldKey: PropertyKey, newKey: PropertyKey) {
    Reflect.set(target, newKey, Reflect.get(target, oldKey))
    Reflect.deleteProperty(target, oldKey)
  }

  /**
   * 生成一个符合 RFC 4122 v4 标准的随机 UUID
   *  - 该方法依赖于 Node.js 环境中内置的 crypto 模块
   * @returns {string} 符合 UUID v4 格式的字符串（例如：'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'）
   */
  public randomUUID(): string {
    return crypto.randomUUID()
  }
}
